vq.BitmapScatterPlot = function() {
    vq.Vis.call(this);
};

vq.BitmapScatterPlot.prototype = vq.extend(vq.Vis);

vq.BitmapScatterPlot.prototype.setRegression = function(obj) {
    this.data._regression = obj || 'none';
    this._render();
};

vq.BitmapScatterPlot.prototype.getScales = function(config) {
    var dataObj = this.data;

    var xScale = d3.scale.linear().domain([config.data_xmin, config.data_xmax]).range([0, dataObj._plot.bitmapWidth]);
    var yScale = d3.scale.linear().domain([config.data_ymin, config.data_ymax]).range([dataObj._plot.bitmapHeight, 0]);

    return {
        x: xScale,
        y: yScale
    };
};

vq.BitmapScatterPlot.prototype.draw = function(data) {
    this.data = new vq.models.BitmapScatterPlotData(data);
    var dataObj = this.data;

    if (!this.data.isDataReady()) { return;}

    var x = dataObj.COLUMNID.x;
    var y = dataObj.COLUMNID.y;

    this.x = x;
    this.y = y;

    this.horizontal_padding = dataObj.horizontal_padding;
    this.vertical_padding = dataObj.vertical_padding;

    this.bitmap_data_left = dataObj.horizontal_padding + dataObj.bitmapHorizontalPadding;
    this.bitmap_data_top = dataObj.vertical_padding + dataObj.bitmapVerticalPadding;

    this.data_array = dataObj.data;

    this.xScale = d3.scale.linear().range([0.0, 1.0]).domain([0.0, 1.0]);
    this.yScale = d3.scale.linear().range([0.0, 1.0]).domain([0.0, 1.0]);

    if (dataObj._plot.emptyContainerDiv) {
        d3.select(dataObj._plot.container).selectAll("div")
            .remove();
    }

    this.vis = d3.select(dataObj._plot.container)
        .append("svg")
        .attr("z-index", 1)
        .attr("width", dataObj._plot.width + 2 * dataObj.horizontal_padding)
        .attr("height", dataObj._plot.height + 2 * dataObj.vertical_padding);

    this.plot = this.vis
        .append("g")
        .attr("transform", "translate(" + dataObj.horizontal_padding + "," + dataObj.vertical_padding + ")")
        .attr("width", dataObj._plot.width)
        .attr("height", dataObj._plot.height);

    // Rectangle around the scale lines.
    this.outer_rect = this.plot
        .append("rect")
        .attr("class", "outer-rect")
        .attr("width", dataObj._plot.width)
        .attr("height", dataObj._plot.height)
        .attr("stroke", "#aaaaaa")
        .attr("fill-opacity", 0)
        .attr("stroke-width", 1.5);

    // Add the Y-axis label
    this.plot.append("g")
        .append("text")
        .attr("class", "axis")
        .text(dataObj.COLUMNLABEL.y)
        .style("font", dataObj._axisFont)
        .style("text-anchor", "middle")
        .attr("transform", "translate(" + dataObj.yLabelDisplacement + "," + dataObj._plot.height / 2 +") rotate(-90)");

    // Add the X-axis label
    this.plot.append("text")
        .attr("class", "axis")
        .text(dataObj.COLUMNLABEL.x)
        .style("font", dataObj._axisFont)
        .attr("x", dataObj._plot.width / 2)
        .attr("y", dataObj._plot.height + dataObj.xLabelDisplacement)
        .style("text-anchor", "middle");

    // Element for horizontal and vertical ticks, superimposed on the bitmap
    this.data_area = this.vis.append("g")
        .attr("class", "data-area")
        .attr("transform", "translate(" + this.bitmap_data_left + "," + this.bitmap_data_top + ")")
        .attr("width", dataObj._plot.bitmapWidth)
        .attr("height", dataObj._plot.bitmapHeight)
        .attr("pointer-events", "all");

    this.data_rect = this.data_area.append("rect")
        .attr("class", "data-rect")
        .attr("width", dataObj._plot.bitmapWidth)
        .attr("height", dataObj._plot.bitmapHeight)
        .attr("stroke", "#bbbbbb")
        .attr("fill-opacity", 0.0)
        .attr("stroke-width", 1.0);


    this.brush = d3.svg.brush();

    this._render();
};

vq.BitmapScatterPlot.prototype.getRegressData = function(scaleInfo) {
    var dataObj = this.data;
    var x = this.x;
    var y = this.y;

    var regress = dataObj._regression;

    if (regress=='linear') {
        var valid_data = this.data.data.filter(function(d, e, f) {
                return (d[y] && d[x]);
            }),
            sum_x = d3.sum(valid_data, function(d) {
                return d[x];
            }),
            sum_y = d3.sum(valid_data, function(d) {
                return d[y];
            }),
            sum_x2 = d3.sum(valid_data, function(d) {
                return d[x] * d[x];
            }),
            sum_xy = d3.sum(valid_data, function(d) {
                return d[x] * d[y];
            }),
            slope = ((valid_data.length * sum_xy) - (sum_x * sum_y)) / ((valid_data.length * sum_x2) - (sum_x * sum_x));

        var intercept = (sum_y - slope * sum_x) / valid_data.length;
    }

    var line_minX = scaleInfo.showMinX * 0.95;
    var line_maxX = scaleInfo.showMaxX * 1.05;
    var line_maxY = slope * line_maxX + intercept;
    var line_minY = slope * line_minX + intercept;

    var lineArray = d3.scale.linear().domain([line_minX, line_maxX]).range([line_minY, line_maxY]);

    return {
        type: regress,
        minX: line_minX,
        maxX: line_maxX,
        scale: lineArray
    };
};

vq.BitmapScatterPlot.prototype._render = function() {
    this.updateScales({
        x: this.xScale,
        y: this.yScale
    }, true);
};

vq.BitmapScatterPlot.prototype.updateScales = function(scaleInfo, disableTransition) {
    var that = this;
    var dataObj = this.data;
    var enable = dataObj.enableTransitions && (!disableTransition),
        y_trans,
        y_trans_enter,
        y_ticks,
        y_ticks_enter,
        y_ticks_exit,
        x_trans,
        x_trans_enter,
        x_ticks,
        x_ticks_enter,
        x_ticks_exit;
    var dr = dataObj.transitionDuration;
    var x, x_mid, y, y_mid;

    x = scaleInfo.x;
    x_mid = (x.domain()[0] + x.domain()[1]) / 2.0;

    y = scaleInfo.y;
    y_mid = (y.domain()[0] + y.domain()[1]) / 2.0;

    // Y-axis ticks
    y_trans = function(d) {
        return "translate(0," + y(d) + ")";
    };

    y_trans_enter = function(d) {
        if (d > y_mid) {
            return "translate(0,0)"
        }
        else {
            return "translate(0," + dataObj._plot.bitmapHeight + ")";
        }
    };

    y_ticks = this.data_area.selectAll("g.y-tick")
        .data(y.ticks(10), String);
    y_ticks_enter = y_ticks.enter();
    y_ticks_exit = y_ticks.exit();

    if (enable) {
        y_ticks = y_ticks.transition()
            .duration(dr);
    }

    y_ticks.attr("transform", y_trans);

    y_ticks_enter = y_ticks_enter.insert("g", "a")
        .attr("class", "y-tick")
        .attr("transform", y_trans_enter)
        .style("opacity", 1e-6);

    y_ticks_enter.append("line")
        .attr("x1", 0)
        .attr("x2", dataObj._plot.bitmapWidth)
        .attr("stroke", "#ccc");

    y_ticks_enter.append("text")
        .attr("x", dataObj.yTickDisplacement)
        .style("font", dataObj._tickFont)
        .attr("text-anchor", "end")
        .text(d3.format("3.2f"));

    if (enable) {
        y_ticks_enter = y_ticks_enter.transition()
            .duration(dr);
        y_ticks_exit = y_ticks_exit.transition()
            .duration(dr)
            .style("opacity", 1e-6);
    }

    y_ticks_enter
        .attr("transform", y_trans)
        .style("opacity", 1.0);

    y_ticks_exit
        .remove();

    // X-axis ticks
    x_trans = function(d) {
        return "translate(" + x(d) + ",0)";
    };

    x_trans_enter = function(d) {
        if (d < x_mid) {
            return "translate(0,0)"
        }
        else {
            return "translate(" + dataObj._plot.bitmapWidth + ",0)";
        }
    };

    x_ticks = this.data_area.selectAll("g.x-tick")
        .data(x.ticks(10), String);
    x_ticks_enter = x_ticks.enter();
    x_ticks_exit = x_ticks.exit();

    if (enable) {
        x_ticks = x_ticks.transition()
            .duration(dr);
    }

    x_ticks.attr("transform", x_trans);

    x_ticks_enter = x_ticks_enter.insert("g", "a")
        .attr("class", "x-tick")
        .attr("transform", x_trans_enter)
        .style("opacity", 1e-6);

    x_ticks_enter.append("line")
        .attr("y1", 0)
        .attr("y2", dataObj._plot.bitmapHeight)
        .attr("stroke", "#ccc");

    x_ticks_enter.append("text")
        .attr("y", dataObj._plot.bitmapHeight + dataObj.xTickDisplacement)
        .style("font", dataObj._tickFont)
        .attr("text-anchor", "middle")
        .text(d3.format("3.2f"));

    if (enable) {
        x_ticks_enter = x_ticks_enter.transition()
            .duration(dr);
        x_ticks_exit = x_ticks_exit.transition()
            .duration(dr)
            .style("opacity", 1e-6);
    }

    x_ticks_enter
        .attr("transform", x_trans)
        .style("opacity", 1.0);

    x_ticks_exit
        .remove();

    this.xScale = x;
    this.yScale = y;
};

vq.BitmapScatterPlot.prototype.updateData = function(config) {
    var dataObj = this.data;
    var width = dataObj._plot.bitmapWidth + 2 * dataObj.bitmapHorizontalPadding;
    var height = dataObj._plot.bitmapHeight + 2 * dataObj.bitmapVerticalPadding;

    this.plot.append("image")
        .attr("width", width)
        .attr("height", height)
        .attr("xlink:href", config.plot_uri);
};

vq.BitmapScatterPlot.prototype.resetData = function(config) {
    var scales = this.getScales(config);
    this.updateScales(scales, false);
    this.updateData(config);
};

vq.BitmapScatterPlot.prototype.removeListeners = function() {
    this.outer_rect
        .on("mousedown.zoom", null)
        .on("mousewheel.zoom", null)
        .on("mousemove.zoom", null)
        .on("DOMMouseScroll.zoom", null)
        .on("dblclick.zoom", null)
        .on("touchstart.zoom", null)
        .on("touchmove.zoom", null)
        .on("touchend.zoom", null);
};

vq.BitmapScatterPlot.prototype.enableBrush = function() {
    this.removeListeners();

    this.brush
        .x(this.xScale)
        .y(this.yScale)
        .on("brush", _.bind(this.brushHandler, this))
        .on("brushend", _.bind(this.brushEnd, this));

    this.data_area.append("g")
        .attr("class", "plot_brush")
        .call(this.brush);
};

vq.BitmapScatterPlot.prototype.brushHandler = function() {

};

vq.BitmapScatterPlot.prototype.brushEnd = function() {
    var e = this.brush.extent();

    this.data._brushHandler(e);
};

vq.models.BitmapScatterPlotData = function(data) {
    vq.models.VisData.call(this, data);
    this.setDataModel();
    if (this.getDataType() == 'vq.models.BitmapScatterPlotData') {
        this._build_data(this.getContents());
    } else {
        console.warn('Unrecognized JSON object.  Expected vq.models.ScatterPlotData object.');
    }
};

vq.models.BitmapScatterPlotData.prototype = vq.extend(vq.models.VisData);

vq.models.BitmapScatterPlotData.prototype.setDataModel = function () {
    this._dataModel = [
        {label: '_plot.width', id: 'PLOT.width', cast : Number, defaultValue: 512},
        {label: '_plot.height', id: 'PLOT.height', cast : Number, defaultValue: 512},
        {label: '_plot.bitmapWidth', id: 'PLOT.bitmap_width', cast : Number, defaultValue: 512},
        {label: '_plot.bitmapHeight', id: 'PLOT.bitmap_height', cast : Number, defaultValue: 512},
        {label: 'xTickDisplacement', id: 'PLOT.x_tick_displacement', cast : Number, defaultValue: 15},
        {label: 'yTickDisplacement', id: 'PLOT.y_tick_displacement', cast : Number, defaultValue: -10},
        {label: 'xLabelDisplacement', id: 'PLOT.x_label_displacement', cast : Number, defaultValue: 30},
        {label: 'yLabelDisplacement', id: 'PLOT.y_label_displacement', cast : Number, defaultValue: -50},
        {label: '_plot.container', id:'PLOT.container', optional : true},
        {label: 'bitmapVerticalPadding', id: 'PLOT.bitmap_vertical_padding', cast : Number, defaultValue: 0},
        {label: 'bitmapHorizontalPadding', id: 'PLOT.bitmap_horizontal_padding',cast : Number,  defaultValue: 0},
        {label: 'vertical_padding', id: 'PLOT.vertical_padding', cast : Number, defaultValue: 40},
        {label: 'horizontal_padding', id: 'PLOT.horizontal_padding',cast : Number,  defaultValue:60},
        {label: 'enableTransitions', id: 'PLOT.enable_transitions', cast : Boolean, defaultValue: false},
        {label: 'transitionDuration', id: 'PLOT.transition_duration', cast : Number, defaultValue: 1000.0},
        {label: '_plot.emptyContainerDiv', id: 'PLOT.empty_container_div', cast : Boolean, defaultValue: false},
        {label : 'COLUMNID.x', id: 'xcolumnid',cast : String, defaultValue : 'X'},
        {label : 'COLUMNID.y', id: 'ycolumnid',cast : String, defaultValue : 'Y'},
        {label : 'COLUMNID.value', id: 'valuecolumnid',cast : String, defaultValue : 'VALUE'},
        {label : 'COLUMNLABEL.x', id: 'xcolumnlabel',cast : String, defaultValue : ''},
        {label : 'COLUMNLABEL.y', id: 'ycolumnlabel',cast : String, defaultValue : ''},
        {label : 'COLUMNLABEL.value', id: 'valuecolumnlabel',cast : String, defaultValue : ''},
        {label : 'COLUMNLABEL.value', id: 'valuecolumnlabel',cast : String, defaultValue : ''},
        {label : 'tooltipItems', id: 'tooltip_items', defaultValue : {
            X : 'X', Y : 'Y', Value : 'VALUE'            }  },
        {label : '_fillStyle', id: 'fill_style',cast :vq.utils.VisUtils.wrapProperty,
            defaultValue : function() {
                return 'rgba(70,130,180,0.2)';
            }},
        {label : '_strokeStyle', id: 'stroke_style',
            cast :vq.utils.VisUtils.wrapProperty, defaultValue : function() {
            return 'steelblue';
        }},
        {label : '_strokeWidth', id: 'stroke_width',cast :vq.utils.VisUtils.wrapProperty, defaultValue : function() {
            return 2;
        }},
        {label : '_radius', id: 'radius',cast :vq.utils.VisUtils.wrapProperty, defaultValue : function() {
            return 2;
        }},
        {label : '_shape', id: 'shape',cast : vq.utils.VisUtils.wrapProperty, defaultValue : function() {
            return 'dot';
        }},
        {label : '_axisFont', id: 'axis_font',cast :vq.utils.VisUtils.wrapProperty, defaultValue : function() {
            return '14px sans';
        }},
        {label : '_tickFont', id: 'tick_font',cast :vq.utils.VisUtils.wrapProperty, defaultValue : function() {
            return '14px sans';
        }},
        {label : '_regression', id: 'regression',cast :String, defaultValue : 'none'},
        {label : '_notifier', id: 'notifier', cast : Function, defaultValue : function() {
            return null;
        }},
        {label : '_brushHandler', id: 'brush_handler', cast : Function, defaultValue : function() {
             return null;
        }}
    ];
};

vq.models.BitmapScatterPlotData.prototype._build_data = function(data) {
    var plot = data.PLOT;

    this._processData(data);

    if (this.COLUMNLABEL.x == '') this.COLUMNLABEL.x = this.COLUMNID.x;
    if (this.COLUMNLABEL.y == '') this.COLUMNLABEL.y = this.COLUMNID.y;
    if (this.COLUMNLABEL.value == '') this.COLUMNLABEL.value = this.COLUMNID.value;

    if ( plot.width < (plot.bitmap_width + 2 * plot.bitmap_horizontal_padding) ) {
        console.warn("Plot width must be equal to or greater than the width of the bitmap area.");
        this.setDataReady(false);
        return;
    }

    if ( plot.height < (plot.bitmap_height + 2 * plot.bitmap_vertical_padding) ) {
        console.warn("Plot height must be equal to or greater than the height of the bitmap area.");
        this.setDataReady(false);
        return;
    }

    this.setDataReady(true);
};
